/**
 * Copyright (c) 2010 deniskir@gmail.com. All rights reserved.
 *
 * @author Denis Kiriusin
 */

package com.dksoftware.combo;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Collection;
import java.util.Collections;
import java.util.Properties;
import java.util.zip.GZIPOutputStream;

import javax.servlet.ServletContext;

import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;

class CIOUtils {

	private CIOUtils() {
		// empty constructor
	}

	/**
	 * The method iterates through the collection of files and returns the last
	 * modified date and time as <tt>long</tt> value.
	 *
	 * @param files
	 *            - collection of files
	 * @return last modified date and time as <tt>long</tt> value, or 0 if files
	 *         collection empty
	 */
	static long lastModified(final Collection<File> files) {
		long lastModified = 0;
		for (File file : files) {
			lastModified = Math.max(lastModified, file.lastModified());
		}
		return lastModified;
	}

	/**
	 * The method receives an array of bytes and returns new array of bytes
	 * compressed using <tt>gzip</tt> compression method.
	 *
	 * @param bytes
	 *            - an array of bytes to compress
	 * @return compressed bytes array
	 * @throws IOException
	 */
	static byte[] gzipContent(final byte[] bytes) throws IOException {
		ByteArrayOutputStream baos = null;
		GZIPOutputStream gzos = null;

		byte[] gzippedBytes = null;

		try {
			baos = new ByteArrayOutputStream();
			gzos = new GZIPOutputStream(baos);

			gzos.write(bytes);
			gzos.finish();
			gzos.flush();
			gzos.close();

			gzippedBytes = baos.toByteArray();
			baos.close();
		} finally {
			if (gzos != null) {
				gzos.close();
			}
			if (baos != null) {
				baos.close();
			}
		}
		return gzippedBytes;
	}

	/**
	 * Copy bytes from an InputStream to an OutputStream. This method buffers
	 * the input internally, so there is no need to use a BufferedInputStream.
	 * The method optionally can add a line break.
	 *
	 * @param input
	 *            - InputStream to read
	 * @param output
	 *            - OutputStream
	 * @param addLineBreak
	 *            - add line break if <tt>true</tt>
	 * @throws IOException
	 */
	static void readInputStream(final InputStream input,
			final OutputStream output, boolean addLineBreak) throws IOException {
		if (input != null) {
			int length = IOUtils.copy(input, output);

			// add line break between files. We don't have to worry about binary
			// files with this as
			// we'll never be merging multiple binary files together in a single
			// request - just text
			// files such as css or javascript files.
			// This fixes an issue where comments could become unbalanced across
			// multiple files if they are merged together
			if (addLineBreak && (length > 0)) {
				output.write('\n');
			}
		}
	}

	/**
	 * The method receives a collection of files and returns byte array of this
	 * collection.
	 *
	 * @param files
	 *            - collection of files
	 * @return byte array of files collection
	 * @throws IOException
	 */
	static byte[] getFilesBytes(final Collection<File> files)
			throws IOException {
		final ByteArrayOutputStream baos = new ByteArrayOutputStream();
		for (File file : files) {
			final InputStream inputStream = new FileInputStream(file);
			try {
				readInputStream(inputStream, baos, (files.size() > 1));
			} finally {
				if (inputStream != null) {
					inputStream.close();
				}
			}
		}
		return baos.toByteArray();
	}

	/**
	 * The method search for files within a given directory (and optionally its
	 * sub directories) which match an collection of extensions.
	 *
	 * @param dir
	 *            - the directory to search in
	 * @param extentions
	 *            - an collection of extensions, ex. {"js","css"}. If this
	 *            parameter is null, all files are returned.
	 * @param recursive
	 *            - if true all sub directories are searched as well
	 * @return an unmodifiable collection of <tt>java.io.File</tt> with the
	 *         matching files
	 */
	static Collection<File> listFiles(File dir,
			final Collection<String> extentions, boolean recursive) {
		return Collections.unmodifiableCollection(FileUtils.listFiles(dir,
				extentions.toArray(new String[0]), recursive));
	}

	/**
	 * Loads <tt>Properties</tt> using current thread's classloader. Here
	 * <tt>filename</tt> is supposed to be placed in one of the roots which are
	 * covered by the default classpath of a webapp, e.g. Webapp/WEB-INF/lib,
	 * Webapp/WEB-INF/classes, Appserver/lib or JRE/lib. If the propertiesfile
	 * is webapp-specific, best is to place it in WEB-INF/classes. If you're
	 * developing a project in an IDE, you can also drop it in src folder (the
	 * project's source folder). <br/>
	 * <br/>
	 * You can alternatively also put it somewhere outside the default classpath
	 * and add its path to the classpath of the appserver. In for example Tomcat
	 * you can configure it as shared.loader property of
	 * Tomcat/conf/catalina.properties.
	 *
	 * @param properties
	 *            - properties to load to
	 * @param filename
	 *            - properties filename
	 * @throws IOException
	 */
	@Deprecated
	static void loadPropertiesFromClasspath(Properties properties,
			String filename) throws IOException {
		properties.load(Thread.currentThread().getContextClassLoader()
				.getResourceAsStream(filename));
	}

	/**
	 * Loads <tt>Properties</tt> using
	 * <tt>ServletContext.getResourceAsStream(path)</tt> method. Use this method
	 * if you want to load properties from Servlet Context, like
	 * /WebContent/WEB-INF.
	 *
	 * @param servletContext
	 *            - ServletContext
	 * @param path
	 *            - path
	 * @return Properties object
	 * @throws IOException
	 */
	static Properties loadPropertiesFromContext(ServletContext servletContext,
			final String path) throws IOException {
		Properties properties = new Properties();
		properties.load(servletContext.getResourceAsStream(path));
		return properties;
	}

	/**
	 * The method loads properties from local disk file. The method closes
	 * FileInputStream after finish loading properties into file.
	 *
	 * @param properties
	 *            - properties to load to
	 * @param file
	 *            - file to load
	 * @throws IOException
	 */
	@Deprecated
	static void loadPropertiesFromFile(Properties properties, File file)
			throws IOException {
		FileInputStream fis = new FileInputStream(file);
		try {
			properties.load(fis);
		} finally {
			fis.close();
		}
	}

	/**
	 * The method stores properties file with additional comment. The method
	 * closes FileOutputStream after finish storing data.
	 *
	 * @param properties
	 *            - properties to store
	 * @param file
	 *            - file
	 * @param comment
	 *            - comment
	 * @throws IOException
	 */
	@Deprecated
	static void storeProperties(Properties properties, File file, String comment)
			throws IOException {
		FileOutputStream fos = new FileOutputStream(file);
		try {
			properties.store(fos, "Properties for Combinatorius");
		} finally {
			fos.close();
		}
	}

	/**
	 * The method receives a path of local the file to return.
	 *
	 * @param path
	 *            - the path of the file
	 * @return File object, never null
	 * @throws MalformedURLException
	 * @throws UnsupportedEncodingException
	 */
	static File getLocalFile(final String path) throws MalformedURLException,
			UnsupportedEncodingException {
		return FileUtils.getFile(path);
	}

	/**
	 * The method gets local file path (<tt>/tmp/somefile.txt</tt>) and
	 * transforms it into local URL (<tt>file:///tmp/somefile.txt</tt>).
	 *
	 * @param path
	 *            - the file path to transform
	 * @return URL, never null
	 * @throws MalformedURLException
	 * @throws UnsupportedEncodingException
	 */
	static URL getLocalFileURL(final String path) throws MalformedURLException,
			UnsupportedEncodingException {
		return new URL("file://" + path);
	}

}
